package com.zh.security.config;

import com.zh.security.handler.MyAuthenticationFailureHandler;
import com.zh.security.handler.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
 * @Description: Security 配置类
 * @ClassName SecurityConfig
 * @date: 2021.10.17 14:44
 * @Author: zhanghang
 *
 * WebSecurityConfigurerAdapter 类是个适配器,我们如果想自定义一下配置的话需要继承这个类，从写他的方法
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private DataSource dataSource;

	@Autowired
	private PersistentTokenRepository persistentTokenRepository;

	@Autowired
	private MyUserDetailsService userDetailsService;

	/**
	 * description: 密码解析器
	 * date: 2021年-10月-17日 14:47
	 * author: zhanghang
	 * SpringSecurity 要求：当自定义登录逻辑时容器内必须有 PasswordEncoder实例，所以不能直接new对象
	 * @param
	 * @return org.springframework.security.crypto.password.PasswordEncoder
	 */
	@Bean
	public PasswordEncoder pw(){
		return new BCryptPasswordEncoder();
	}


	/**
	 * description: 这个方法是关于 http 请求的认证策略方面的设置
	 * date: 2021年-10月-17日 15:23
	 * author: zhanghang
	 *
	 * @param http
	 * @return void
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 关闭csrf
		http.csrf().disable();

		// 表单提交
		http.formLogin()
				// 自定义入参，必须和表单提交的参数名一致
				.usernameParameter("username123")
				.passwordParameter("password123")
				// 自定义登录页面
				.loginPage("/login.html")
				// 必须和 表单提交的接口一致，才会执行自定义登录逻辑
				.loginProcessingUrl("/login")
				// 登录成功后跳转的页面, 否则默认跳转的是 "/index.html"
				// 因为自定义的只接受post请求，所以我们这里需要跳转到接口
				.successForwardUrl("/toMain")
				// 登录成功后跳转使用自定义的处理器
//				.successHandler(new MyAuthenticationSuccessHandler("/toMain"))
				// 登录失败跳转的请求
				.failureForwardUrl("/toError");
				// 登录失败后跳转使用自定义的处理器
//				.failureHandler(new MyAuthenticationFailureHandler("/error.html"));

		/*
		 * 匹配URL方法说明：
		 * .antMatchers(String... antPatterns): 匹配路径
		 *   ? :表示匹配一个字符
		 * 	 * :表示匹配0个或多个字符
		 *   **:表示匹配0个或多个目录
		 * .anyRequest()： 表示匹配所有路径
		 * .regexMatchers(): 表示使用正则表达式匹配
		 *
		 * 权限控制方法说明：
		 * .permitAll() : 表示允许所有可以访问
		 * .authenticated(): 表示登录成功后才允许访问
		 * .denyAll(): 表示禁止所有访问
		 * .anonymous(): 表示匿名访问
		 * .fullyAuthenticated(): 表示必须是输入用户名和密码进来的才可以访问，如果是rememberMe进来的不允许访问
		 * .rememberMe(): 表示记住我
		 *
		 * .hasAuthority(String authority): 表示匹配到的路径必须拥有指定的权限才能访问，区分大小写
		 * .hasAnyAuthority(String... authorities): 表示匹配到的路径只要拥有指定的权限中的一个都可以访问
		 * .hasRole(String role): 表示匹配到的路径必须拥有指定的角色才能访问，区分大小写
		 * .hasAnyRole(String... roles): 表示匹配到的路径只要拥有指定的角色中的一个都可以访问
		 * .hasIpAddress("127.0.0.1"): 表示必须是该ip才能访问
		 */
		// 授权
		http.authorizeRequests()
				// 放行 login.html 请求
				.antMatchers("/login.html").permitAll()
				// 放行 error.html 请求
				.antMatchers("/error.html").permitAll()
				// 使用access 来过滤
//				.antMatchers("/toMain").access("hasAuthority('admin')")
				// 使用正则匹配 .png ，所有匹配到的禁止访问
//				.regexMatchers(".+[.]png").denyAll()
				// 指定url 必须拥有 admin权限才能访问
//				.antMatchers("/hello").hasAnyAuthority("admin123")
//				.antMatchers("/main1.html").hasAnyAuthority("admin","abc")
//				.antMatchers("/main1.html").hasAnyRole("123","bcd")
//				.antMatchers("/main1.html").hasIpAddress("127.0.0.1")
				// 所有请求必须认证才能访问
				.anyRequest().authenticated();

		// 记住我功能
		http.rememberMe()
				// 存放数据源
				.tokenRepository(persistentTokenRepository)
				// 自定义remember-me 字段
//				.rememberMeParameter("rememberMe")
				// 超时时间，默认为2周
				.tokenValiditySeconds(60)
				// 自定义的登录逻辑
				.userDetailsService(userDetailsService);

		// 退出登录
		http.logout()
				// 设置退出成功后跳转的页面
				.logoutSuccessUrl("/login.html");


	}

	/**
	 * description: 数据源
	 * date: 2021年-10月-24日 19:16
	 * author: zhanghang
	 *
	 * @param
	 * @return org.springframework.security.web.authentication.rememberme.PersistentTokenRepository
	 */
	@Bean
	public PersistentTokenRepository persistentTokenRepository(){
		JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
		// 设置数据源
		jdbcTokenRepository.setDataSource(dataSource);
		// 自动建表，注意：第一次启动时开启，第二次启动时关闭
//		jdbcTokenRepository.setCreateTableOnStartup(true);
		return jdbcTokenRepository;
	}
}
